package com.ms.service.module.account;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ms.game.common.container.StringTableBinUtil;
import com.ms.game.common.utility.SimpleTimer;
import com.ms.game.gmtool.message.SGmtoolServerShutdownCountdownNotify;
import com.ms.player.Player;
import com.ms.player.PlayerNickNameParser;
import com.ms.service.error.ErrorCode;
import com.ms.service.module.account.db.AccountDAO;
import com.ms.service.module.account.db.ProxyList;
import com.ms.service.module.account.db.TcpProxyServerGroupListModel;
import com.ms.service.module.account.message.SSessionCloseNotify;
import com.ms.service.telnet.STelnetCmd;
import com.ms.service.telnet.TelGUID;
import com.xpec.c4.db.dao.PlayerDAO;
import com.xpec.c4.game.time.TimeConstants;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.game.utility.ProcessSpeedCalculator;
import com.xpec.c4.service.module.BasicServiceModule;
import com.xpec.c4.service.module.ConsistentGroupName;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.ServerProperties;

public class AccountModule extends BasicServiceModule 
{
	/** log4j object */
	private static final Log log = LogFactory.getLog(AccountModule.class);
	
	/** 是否允許登入 */
	private boolean allowLogin = ServerProperties.getBoolean("account_allow_login", true);
	
	/** 是否允許用白名單登入 */
	private boolean allowWhiteListLogin = ServerProperties.getBoolean("account_allow_whitelist", true);

	/** 立即玩帳號的名稱格式 */
	public static final String TRIAL_ACCOUNT_NAME = "TRIAL_PLAYER";
	
	/** 綁定裝置立即玩帳號的名稱格式(前綴) */
	public static final String TRIAL_DEVICE_ACCOUNT_NAME = "TRIAL_PLAYER_DEVICE_";
	
	/**
	 * 立即玩帳號的角色名稱<br>
	 * key: trialAccountGroup<br>
	 * value: 名稱
	 */
	private static HashMap<Integer, String> trialPlayerNames = new HashMap<Integer, String>();
	private static boolean isTrialPlayerNamesInit = false;
	
	/**
	 * 目前的 account module 管理的立即玩帳號 id 清單<br>
	 * 初始化的時候載入一次, 不會再變更, 單純用來比對用的
	 */
	private HashSet<GUID> ownTrialAccountId = new HashSet<GUID>();

	/** 未使用的的立即玩帳號清單 <account guid> */
	private ConcurrentLinkedQueue<GUID> unusedTrialAccountList = new ConcurrentLinkedQueue<GUID>();
	
	/**
	 * 體驗帳號管理員
	 */
	private TrialAccountManager trialAccountManager = new TrialAccountManager();

	/** proxy server list from db */
	private ArrayList<TcpProxyServerGroupListModel> proxyGroupList;

	/** 維護用的 proxylist */
	private ArrayList<String> maintainProxyList = new ArrayList<String>();
	
	/** 維護用目前輪詢到的 proxy index */
	private int maintainProxyIndex = 0;

	/** 維護用的 h5proxylist */
	private ArrayList<String> maintainH5ProxyList = new ArrayList<String>();
	
	/** 維護用目前輪詢到的 h5 proxy index */
	private int maintainH5ProxyIndex = 0;
	
	/** 預設的 proxylist ，有不認識的國別會預設導入邊*/
	private ArrayList<String> defaultProxyList = new ArrayList<String>();
	
	/** 預設目前輪詢到的 proxy index */
	private int defaultProxyIndex = 0;

	/** 預設的 h5proxylist ，有不認識的國別會預設導入邊*/
	private ArrayList<String> defaultH5ProxyList = new ArrayList<String>();
	
	/** 預設目前輪詢到的 H5 proxy index */
	private int defaultH5ProxyIndex = 0;
	
	/** 各國別的 proxy group 中的 server list */
	private HashMap<String, ArrayList<String>> proxyListMap = new HashMap<String, ArrayList<String>>();

	/** 各國別目前輪詢到的 proxy index */
	private HashMap<String, Integer> proxyIndexMap = new HashMap<String, Integer>();

	/** 各國別的 H5 proxy group 中的 server list */
	private HashMap<String, ArrayList<String>> h5ProxyListMap = new HashMap<String, ArrayList<String>>();

	/** 各國別目前輪詢到的 H5 proxy index */
	private HashMap<String, Integer> h5ProxyIndexMap = new HashMap<String, Integer>();
	
	/** 處理刷新各proxyList用的鎖 */
	private byte[] proxyLock = new byte[0];
	
	/* (non-Javadoc)
	 * @see com.xpec.c4.service.module.IServiceModule#init()
	 */
	@Override
	public void init() 
	{
		super.init();
		registerActionClass(AccountAction.class);
		
		initTrialAccountPlayerName();
	}

	/* (non-Javadoc)
	 * @see com.xpec.c4.service.module.IServiceModule#start()
	 */
	@Override
	public boolean startup() 
	{
		// 每分鐘固定從DB載入proxyList
		UpdateProxyListTimmer taskTimmer = new UpdateProxyListTimmer();
		taskTimmer.setModule(this);
		taskTimmer.setCronExpressions("0 0/1 * 1/1 * ? *");//每分鐘
		this.addCronTaskTimer(taskTimmer);
		
		//每分鐘檢查體驗帳號回收
		TrialAccountManagerTimmer trialAccountManagerTimmer = new TrialAccountManagerTimmer();
		trialAccountManagerTimmer.setModuleServer(this);
		this.addCronTaskTimer(trialAccountManagerTimmer);
		
		//提供體驗帳號管理員modul的實體
		trialAccountManager.setOwnModule(this);
		
		return super.startup();
	}
	
	@Override
	public void onModuleServerReadyStart() {
		super.onModuleServerReadyStart();
		
		refreshProxyList();
	}

	/**
	 * 載入可以使用的立即玩帳號 id
	 * @param curAccountServNum
	 */
	public void loadTrialAccounts(int curAccountServNum) {
		// 取得所有的立即玩帳號 id 清單
		List<GUID> trialAccountIdList = AccountDAO.loadUnusedTrialAccount();

		// 收集範圍內的資料
		String curAccountModuleId = getModuleName();
		ArrayList<GUID> availableArray = new ArrayList<GUID>();

		for (GUID trialAccountId : trialAccountIdList) {
			String targetAccountModuleId = ConsistentGroupName.lookUp(ModuleName.GROUP_ACCOUNT_PLAYER,
					trialAccountId);
			if (targetAccountModuleId.equals(curAccountModuleId)) {
				availableArray.add(trialAccountId);
			}
		}

		// 打亂
		Collections.shuffle(availableArray);
		
		// 加到真正的清單內
		unusedTrialAccountList.addAll(availableArray);
		
		// 加到驗證清單內
		for(GUID accountId:availableArray){
			ownTrialAccountId.add(accountId);
		}
	}

	/**
	 * 體驗帳號角色名稱初始化
	 */
	public static void initTrialAccountPlayerName(){
		
		for(TrialAccountGroup group : TrialAccountGroup.values()){
			String name = StringTableBinUtil.getText(group.getStringTableID());
			if(name != null){
				trialPlayerNames.put(group.getGroupID(), name);
			}
		}
		
		isTrialPlayerNamesInit = true;
	}
	
	/**
	 * 建立所有立即玩帳號<br>
	 * trialAccountGroupID: 創建的群組編號
	 */
	public static void createAllTrialAccounts(int trialAccountGroupID) {
		SimpleTimer st = new SimpleTimer();
		ProcessSpeedCalculator psc = new ProcessSpeedCalculator();

		//先初始化體驗帳號群組
		initTrialAccountPlayerName();
		
		ArrayList<Integer> nameNumberList = new ArrayList<Integer>();
		for (int nameNumber = 0; nameNumber <= TrialAccountGroup.TRIAL_PLAYER_MAX_LIMIT_PER_GROUP; nameNumber++) {
			String numberStr = String.valueOf(nameNumber);
			if (numberStr.contains("4")) {
				continue;
			}

			nameNumberList.add(nameNumber);
		}

		// 初始計時器
		st.startByRemainTime(1 * TimeConstants.MILLI_SECOND_PRE_SECOND);

		// 初始處理速度計算器
		psc.setEstimateTotalProcCount(nameNumberList.size());
		psc.start();

		// 初始立即玩編號
		int trialNum = 0;

		// 將所有的試玩編號都檢查一遍
		for (int nameNum : nameNumberList) {
			trialNum++;
			String accountCoopId = AccountDAO.getTrialAccountName(trialAccountGroupID, trialNum);

			// 載入對應試玩編號的 account model
			Account account = AccountDAO.loadAccountByCooperatorAccountId(accountCoopId);

			// 是否需要建立角色
			boolean needCreatePlayer = false;

			// 有帳號資料
			if (account != null) {
				// 檢查裡面有沒有角色
				if (account.getPlayerCount() == 0) {
					// 準備建立角色
					needCreatePlayer = true;
				}
			} else {
				// 沒有帳號資料
				// 建立帳號
				account = AccountDAO.createTrialAccountByTrialNumber(trialAccountGroupID, trialNum);
				if (account == null) {
					if (log.isErrorEnabled()) {
						log.error("createAllTrialAccounts(): create trial account fail. trialNum=" + trialNum);
					}
					continue;
				}

				needCreatePlayer = true;
			} // if(account != null) else

			if (needCreatePlayer) {
				// 假設先前建立立即玩角色過程有中斷, 先用名字取資料看有沒有現成的
				String nameNumStr = PlayerNickNameParser.nameNumberToString(nameNum);
				String playerName = PlayerNickNameParser.mergeNickName(AccountModule.getTrialPlayerName(trialAccountGroupID), nameNumStr);
				Player player = PlayerDAO.loadPlayerByName(playerName);

				if (player == null) {
					// 取不到就建一個新的 player
					player = PlayerDAO.createTrialPlayer(account.getAccountId(), trialAccountGroupID, nameNum);
					if (player == null) {
						if (log.isErrorEnabled()) {
							log.error("createAllTrialAccounts(): create trial player fail. trialNum=" + trialNum);
						}
						continue;
					}
				}

				// 將 player 加入 account
				account.addPlayer(player.getGuid());

				// 存入 db
				GameUtility.writeCashUpdateConsoleLog("AccountModule.createAllTrialAccounts()", account.getAccountId().toString(), 0, 0, account.getCash());
				if (!AccountDAO.saveOrUpdateAccount(account)) {
					log.error("createAllTrialAccounts(): trial account add player fail. trialNum=" + nameNum);
					continue;
				}
			} // if (needCreatePlayer) {

			psc.increaseProcCount();

			if (st.update() || psc.getTotalProcCount() == nameNumberList.size()) {
				psc.calculateSpeed();

				if (log.isInfoEnabled()) {
					long remainTime = psc.getRemainTime();
					String remianTimeStr = GameUtility.remainTimeToString(remainTime);

					long finishTime = System.currentTimeMillis() + remainTime;
					String finishTimeStr = GameUtility.timeToString(finishTime, GameUtility.TIME_FORMAT_FULL);

					log.info("totalCreate=" + psc.getTotalProcCount() + " Speed=" + psc.getProcSpeed() + "/sec"
							+ " remainTime=" + remianTimeStr + " finishTime=" + finishTimeStr);
				}

				st.restart();
			}

		} // for (int trialNum : nameNumberList) {
	}
	
	/**
	 * 角色斷線
	 * 
	 * @param notify
	 */
	public void recvSessionCloseNotify(SSessionCloseNotify notify) {
		// 斷線的帳號是這個 account module 管理的立即玩帳號
		if (ownTrialAccountId.contains(notify.getAccountId())) {
			
			trialAccountManager.removeLendOutPlayer(notify.getAccountId());
			trialAccountManager.removeLoginSuccessAccount(notify.getAccountId());
			
			ReleaseTrialAccountIdAsync task = new ReleaseTrialAccountIdAsync(this, notify.getAccountId());
			this.addAsyncCommand(task);
		}
	}
	
	/**
	 * 加入一個成功取出的體驗帳號角色
	 */
	public void addLendOutTrialAccountIDandPlayerGUID(GUID accountID, GUID playerGUID){
		if(ownTrialAccountId.contains(accountID)){
			trialAccountManager.addLendOutPlayer(accountID, playerGUID);
		}
	}
	
	/**
	 * 加入一個成功登入的體驗帳號
	 */
	public void addLoginSuccessTrialAccountID(GUID accountID){
		if(ownTrialAccountId.contains(accountID)){
			trialAccountManager.addLoginSuccessAccount(accountID);
		}
	}
	
	/**
	 * 計時器tick檢查可回收的帳號
	 */
	public void onTimmerTickCheckTrialAccounts(){
		int originSize = unusedTrialAccountList.size();
		
		//取得檢查完畢應該歸還的帳號
		ArrayList<GUID> returnAccountIDs = trialAccountManager.checkLendOutAccounts();
		
		for(GUID accountID : returnAccountIDs){
			
			//加回未使用的帳號中
			if(ownTrialAccountId.contains(accountID) && !unusedTrialAccountList.contains(accountID)){
				unusedTrialAccountList.add(accountID);
			}
		}
		
		if(log.isInfoEnabled()){
			log.info("onTimmerTickCheckTrialAccounts result: "+originSize+" / "+returnAccountIDs.size()+" / "+unusedTrialAccountList.size());
		}
	}

	/* (non-Javadoc)
	 * @see com.xpec.c4.service.module.IServiceModule#stop()
	 */
	@Override
	public void shutdown() 
	{
		super.shutdown();
	}
	
	/**
	 * 
	 * @param guid
	 */
	private void feedbackStatus(TelGUID guid){

		String stat = "AccountModule status\r\nAllow Login: " + allowLogin
				+ "\r\nAllow WhiteList: " + allowWhiteListLogin + "\r\n";
		
		telnetString(guid, stat);
	}

	@Override
	public void procTelnetCommand(STelnetCmd obj) {
		
		if(obj.command == null || obj.command.length() < 1){
			// show status			
			feedbackStatus(obj.guid);
			return ;
		}
		
		String cmd [] = obj.command.split(" ");
		
		if(cmd.length < 1){
			// 沒參數就顯示狀態
			feedbackStatus(obj.guid);
			return;
		}
		
		if(cmd[0].equals("on") ){
			allowLogin = true;
			telnetString(obj.guid, "Account Allow Login: " + allowLogin + "\r\n");
		}
		else if(cmd[0].equals("off") ){
			allowLogin = false;
			telnetString(obj.guid, "Account Allow Login: " + allowLogin + "\r\n");
		}
		else if(cmd[0].equals("white") ){
			allowWhiteListLogin = !allowWhiteListLogin;
			telnetString(obj.guid, "Account allow WhiteList Login: " + allowWhiteListLogin + "\r\n");
		}else{
			feedbackStatus(obj.guid);
		}
		return;
	}

	public boolean isAllowLogin() {
		return allowLogin;
	}

	public void setAllowLogin(boolean allowLogin) {
		this.allowLogin = allowLogin;
	}

	public boolean isAllowWhiteListLogin() {
		return allowWhiteListLogin;
	}

	public void setAllowWhiteListLogin(boolean allowWhiteListLogin) {
		this.allowWhiteListLogin = allowWhiteListLogin;
	}

	public ConcurrentLinkedQueue<GUID> getUnusedTrialAccountList() {
		return unusedTrialAccountList;
	}

	public void setUnusedTrialAccountList(ConcurrentLinkedQueue<GUID> unusedTrialAccountList) {
		this.unusedTrialAccountList = unusedTrialAccountList;
	}

	@Override
	public void procThrowException(Object obj) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * 載入所有的 proxy group 資料<br>
	 * 每次載入時會盡量維持原本index
	 */
	public void refreshProxyList() {
		if(log.isInfoEnabled()){
			log.info("load TcpProxyServerGroupListModel begin");
		}
		
		ArrayList<TcpProxyServerGroupListModel> NEW_proxyGroupList = AccountDAO.loadTcpProxyList();
		if(GameUtility.getArrayListSize(NEW_proxyGroupList) == 0){
			if(log.isErrorEnabled()){
				log.error("load TcpProxyServerGroupListModel interrupt, tcp_proxy_group_list is empty");
			}
			return;
		}

		//判斷一樣的話就不用做事
		if(GameUtility.getArrayListSize(proxyGroupList) == NEW_proxyGroupList.size()){
			int equalsCount = 0;
			for(TcpProxyServerGroupListModel model : NEW_proxyGroupList){
				int id = model.getId();
				for(TcpProxyServerGroupListModel listModel : proxyGroupList){
					if(listModel.getId() == id){
						if(listModel.equals(model)) equalsCount++;
						break;
					}
				}
			}
			if(equalsCount == proxyGroupList.size()){
				if(log.isInfoEnabled()){
					log.info("load TcpProxyServerGroupListModel interrupt, nothing different after last refresh");
				}
				return;
			}
		}
		
		//載入新的groupList
		proxyGroupList = NEW_proxyGroupList;

		//用thread save的方式對各Map處理，避免在timmer更新瞬間有可能拿不到正確的
		synchronized (proxyLock) {
			maintainProxyList.clear();
			defaultProxyList.clear();
			proxyListMap.clear();

			maintainH5ProxyList.clear();
			defaultH5ProxyList.clear();
			h5ProxyListMap.clear();

			boolean isMaintainAlreadySet = false;
			boolean isDefaultAlreadySet = false;

			boolean isMaintainH5AlreadySet = false;
			boolean isDefaultH5AlreadySet = false;
			
			//將proxyList放入各Map
			for(TcpProxyServerGroupListModel model : proxyGroupList){

				//DB ID
				int id = model.getId();
				//proxy類型
				int proxyType = model.getProxyType();
				//國別
				String country = model.getCountry();
				
				//檢查是不是合法的proxyType
				if(!model.LegalProxyType()){
					if(log.isErrorEnabled()){
						log.error("groupID: "+id+" is illegal proxyType, proxyType: "+proxyType);
					}
					continue;
				}
				
				//檢查model.getProxyList()內容是否合法
				String proxyGroupStr = model.getProxyList();
				if (!GameUtility.isHaveString(proxyGroupStr)) {
					if(log.isErrorEnabled()){
						log.error("ErrorCode: "+ ErrorCode.LOGIN_PROXY_GROUP_IS_EMPTY + ", proxyList is empty, groupID: "+id);
					}
					continue;
				}
				
				//目前accountModule這邊只取ragularProxyList(經過檢驗正常的)，problemProxyList只交給檢驗機制去檢查
				ArrayList<String> ragularProxyList = ProxyList.fromJsonStringToClass(proxyGroupStr).getRagularProxyList();
				if(ragularProxyList == null){
					if(log.isErrorEnabled()){
						log.error("can't convert content to ragularProxyList, groupID: "+id);
					}
					continue;
				}
				ArrayList<String> ragularH5ProxyList = ProxyList.fromJsonStringToClass(proxyGroupStr).getRagularH5ProxyList();
				if(ragularH5ProxyList == null){
					if(log.isErrorEnabled()){
						log.error("can't convert content to ragularH5ProxyList, groupID: "+id);
					}
					continue;
				}
				
				//各proxyType處理
				switch (proxyType) {
				//維護用的proxyList
				case TcpProxyServerGroupListModel.PROXY_TYPE_MAINTAIN: {
					if(!isMaintainAlreadySet){
						maintainProxyList = ragularProxyList;
						
						//載入時盡量維持原本index
						if(maintainProxyIndex >= maintainProxyList.size())
							maintainProxyIndex = 0;
						
						isMaintainAlreadySet = true;
					}
					else{
						if(log.isErrorEnabled()){
							log.error("More than One ProxyGroup for maintain, groupID: "+id+" (this group setting is abandoned)");
						}
					}
					if(!isMaintainH5AlreadySet){
						maintainH5ProxyList = ragularH5ProxyList;
						
						//載入時盡量維持原本index
						if(maintainH5ProxyIndex >= maintainH5ProxyList.size())
							maintainH5ProxyIndex = 0;
						
						isMaintainH5AlreadySet = true;
					}
					else{
						if(log.isErrorEnabled()){
							log.error("More than One H5ProxyGroup for maintain, groupID: "+id+" (this group setting is abandoned)");
						}
					}
					
					continue;
				}
				
				//預設的proxyList
				case TcpProxyServerGroupListModel.PROXY_TYPE_DEFAULT: {
					if(!isDefaultAlreadySet){
						defaultProxyList = ragularProxyList;
						
						//載入時盡量維持原本index
						if(defaultProxyIndex >= defaultProxyList.size())
							defaultProxyIndex = 0;
						
						isDefaultAlreadySet = true;
					}
					else{
						if(log.isErrorEnabled()){
							log.error("More than One ProxyGroup for default, groupID: "+id+" (this group setting is abandoned)");
						}
					}
					if(!isDefaultH5AlreadySet){
						defaultH5ProxyList = ragularH5ProxyList;
						
						//載入時盡量維持原本index
						if(defaultH5ProxyIndex >= defaultH5ProxyList.size())
							defaultH5ProxyIndex = 0;
						
						isDefaultH5AlreadySet = true;
					}
					else{
						if(log.isErrorEnabled()){
							log.error("More than One H5ProxyGroup for default, groupID: "+id+" (this group setting is abandoned)");
						}
					}
					
					continue;
				}
				
				//各國別的proxyList
				case TcpProxyServerGroupListModel.PROXY_TYPE_FORMAL: {
					//沒設定國別就跳過
					if(country == null){
						if(log.isErrorEnabled()){
							log.error("formal proxy group without setting country, groupID: "+id);
						}
						continue;
					}
					
					if(proxyListMap.get(country) == null){
						proxyListMap.put(country, ragularProxyList);
						
						//載入時盡量維持原本index
						if(proxyIndexMap.get(country) == null)
							proxyIndexMap.put(country, 0);
						else{
							int curIndex = proxyIndexMap.get(country);
							if(curIndex >= proxyListMap.get(country).size())
								curIndex = 0;
							
							proxyIndexMap.put(country, curIndex);
						}
						
					}
					//每個國別應該只會有一個group設定，又發現有的話就寫log然後忽略
					else{
						if(log.isErrorEnabled()){
							log.error("More than One ProxyGroup for country: "+country+", where groupID: "+id+" (this group setting is abandoned)");
						}
					}

					if(h5ProxyListMap.get(country) == null){
						h5ProxyListMap.put(country, ragularH5ProxyList);
						
						//載入時盡量維持原本index
						if(h5ProxyIndexMap.get(country) == null)
							h5ProxyIndexMap.put(country, 0);
						else{
							int curH5Index = h5ProxyIndexMap.get(country);
							if(curH5Index >= h5ProxyListMap.get(country).size())
								curH5Index = 0;
							
							h5ProxyIndexMap.put(country, curH5Index);
						}
						
					}
					//每個國別應該只會有一個group設定，又發現有的話就寫log然後忽略
					else{
						if(log.isErrorEnabled()){
							log.error("More than One H5ProxyGroup for country: "+country+", where groupID: "+id+" (this group setting is abandoned)");
						}
					}
					
					continue;
				}

				//不認識的proxyType，應該是不會到這邊，在上面有先檢查過
				default:
					break;
				}
				
			}//for(TcpProxyServerGroupListModel model : proxyGroupList){
			
			//處理完之後檢查一下有沒有多的indexMap，多的就直接丟掉
			ArrayList<String> deleteCountry = new ArrayList<String>();
			for(Entry<String, Integer> entry : proxyIndexMap.entrySet()){
				String country = entry.getKey();
				if(proxyListMap.get(country) == null){
					deleteCountry.add(country);
				}
			}
			for(String country : deleteCountry){
				proxyIndexMap.remove(country);
			}
			ArrayList<String> deleteH5Country = new ArrayList<String>();
			for(Entry<String, Integer> entry : h5ProxyIndexMap.entrySet()){
				String country = entry.getKey();
				if(h5ProxyListMap.get(country) == null){
					deleteH5Country.add(country);
				}
			}
			for(String country : deleteH5Country){
				h5ProxyIndexMap.remove(country);
			}
			
		}//synchronized (proxyLock) {
	
		//紀錄一下操作完畢的結果
		if(log.isInfoEnabled()){
			log.info("load TcpProxyServerGroupListModel complete");
			
			log.info("current maintain ProxyList(index/size): "+maintainProxyIndex+"/"+maintainProxyList.size());
			log.info("current default ProxyList(index/size): "+defaultProxyIndex+"/"+defaultProxyList.size());
			log.info("current maintain H5 ProxyList(index/size): "+maintainH5ProxyIndex+"/"+maintainH5ProxyList.size());
			log.info("current default H5 ProxyList(index/size): "+defaultH5ProxyIndex+"/"+defaultH5ProxyList.size());
			
			for(Entry<String, ArrayList<String>> entry : proxyListMap.entrySet()){
				String country = entry.getKey();
				int index = proxyIndexMap.get(country);
				int size = entry.getValue().size();
				log.info("country: "+country+" current ProxyList(index/size): "+index+"/"+size);
			}
			for(Entry<String, ArrayList<String>> entry : h5ProxyListMap.entrySet()){
				String country = entry.getKey();
				int index = h5ProxyIndexMap.get(country);
				int size = entry.getValue().size();
				log.info("country: "+country+" current H5 ProxyList(index/size): "+index+"/"+size);
			}
		}
		
		if(maintainProxyList == null || maintainProxyList.size() == 0){
			if(log.isErrorEnabled())
				log.error("Maintain ProxyList is empty!!!");
		}
		
		if(defaultProxyList == null || defaultProxyList.size() == 0){
			if(log.isErrorEnabled())
				log.error("Default ProxyList is empty!!!");
		}

		if(maintainH5ProxyList == null || maintainH5ProxyList.size() == 0){
			if(log.isErrorEnabled())
				log.error("Maintain H5 ProxyList is empty!!!");
		}
		
		if(defaultH5ProxyList == null || defaultH5ProxyList.size() == 0){
			if(log.isErrorEnabled())
				log.error("Default H5 ProxyList is empty!!!");
		}
	}

	/**
	 * (1)取得目前輪到的 proxy server ip 和 port<br>
	 * (2)切換到下一個index<br>
	 * @note 如果傳入一個列表之外的國別會自動提供default的ip和port，如果都拿不到會得到兩個空字串
	 */
	public String[] getCurProxyIpAndPort(int type, String country, boolean h5Status) {
		String[] IpAndPort = {"", ""};
		synchronized (proxyLock) {
			switch (type) {
			case TcpProxyServerGroupListModel.PROXY_TYPE_MAINTAIN:{
				if(!h5Status && maintainProxyList != null && maintainProxyList.size() > 0 && maintainProxyList.get(maintainProxyIndex) != null){
					//取得IP跟PORT
					IpAndPort = maintainProxyList.get(maintainProxyIndex).split(":");
					
					//index切換到下一個
					maintainProxyIndex++;
					if(maintainProxyIndex >= maintainProxyList.size()){
						maintainProxyIndex = 0;
					}
				} else if (h5Status && maintainH5ProxyList != null && maintainH5ProxyList.size() > 0 && maintainH5ProxyList.get(maintainH5ProxyIndex) != null) {
					//取得IP跟PORT
					IpAndPort = maintainH5ProxyList.get(maintainH5ProxyIndex).split(":");
					
					//index切換到下一個
					maintainH5ProxyIndex++;
					if(maintainH5ProxyIndex >= maintainH5ProxyList.size()){
						maintainH5ProxyIndex = 0;
					}
				}
				break;
			}
			case TcpProxyServerGroupListModel.PROXY_TYPE_FORMAL:{
				if(country == null){
					break;
				}

				//如果該國別有資料的話就提供，如果該國別能用的list是0個也會導到default
				if(!h5Status && proxyListMap != null && proxyListMap.get(country) != null && proxyListMap.get(country).size() > 0 &&
				   proxyIndexMap.get(country) != null){
					//取得IP跟PORT
					int curCountryIndex = proxyIndexMap.get(country);
					if(proxyListMap.get(country).get(curCountryIndex) != null)
						IpAndPort = proxyListMap.get(country).get(curCountryIndex).split(":");
					
					//index切換到下一個
					int curIndex = proxyIndexMap.get(country);
					curIndex++;
					if(curIndex >= proxyListMap.get(country).size()){
						curIndex = 0;
					}
					proxyIndexMap.put(country, curIndex);
				}
				//沒有的話就提供DEFAULT(TcpProxyServerGroupListModel.PROXY_TYPE_DEFAULT)
				else if (!h5Status && defaultProxyList != null && defaultProxyList.size() > 0
						&& defaultProxyList.get(defaultProxyIndex) != null){
						//取得IP跟PORT
						IpAndPort = defaultProxyList.get(defaultProxyIndex).split(":");
						
						//index切換到下一個
						defaultProxyIndex++;
						if(defaultProxyIndex >= defaultProxyList.size()){
							defaultProxyIndex = 0;
						}
				} else if (h5Status && h5ProxyListMap != null && h5ProxyListMap.get(country) != null && h5ProxyListMap.get(country).size() > 0 &&
						h5ProxyIndexMap.get(country) != null) {
					//取得IP跟PORT
					int curCountryIndex = h5ProxyIndexMap.get(country);
					if(h5ProxyListMap.get(country).get(curCountryIndex) != null)
						IpAndPort = h5ProxyListMap.get(country).get(curCountryIndex).split(":");
					
					//index切換到下一個
					int curIndex = h5ProxyIndexMap.get(country);
					curIndex++;
					if(curIndex >= h5ProxyListMap.get(country).size()){
						curIndex = 0;
					}
					h5ProxyIndexMap.put(country, curIndex);
					
				} else if (h5Status && defaultH5ProxyList != null && defaultH5ProxyList.size() > 0
						&& defaultH5ProxyList.get(defaultH5ProxyIndex) != null) {
						//取得IP跟PORT
						IpAndPort = defaultH5ProxyList.get(defaultH5ProxyIndex).split(":");
						
						//index切換到下一個
						defaultH5ProxyIndex++;
						if(defaultH5ProxyIndex >= defaultH5ProxyList.size()){
							defaultH5ProxyIndex = 0;
						}
				}
				
				break;
			}

			default:
				break;
			}
		}//synchronized (proxyLock) {
		
		if(IpAndPort.length == 2)
			return IpAndPort;
		else{
			//如果.split(":")出來的長度很奇怪也當作沒找到
			String[] empty = {"", ""};
			return empty;
		}
	}

	public HashSet<GUID> getOwnTrialAccountId() {
		return ownTrialAccountId;
	}
	
	@Override
	public void onRecvServerShutdownCountdownNotify(SGmtoolServerShutdownCountdownNotify notify) {
		super.onRecvServerShutdownCountdownNotify(notify);
		
	}

	/**
	 * 取得體驗帳號的角色名稱
	 * 
	 * @return
	 */
	public static String getTrialPlayerName(int trialAccountGroup) {
		if(!isTrialPlayerNamesInit){
			initTrialAccountPlayerName();
		}
		
		String name = trialPlayerNames.get(trialAccountGroup);
		if (name != null) {
			return name;
		}

		String numStr = GameUtility.formatNumber(trialAccountGroup, "00");
		return "TRIAL"+numStr;
	}
	
	/**
	 * 是否是系統內建的體驗角色的名稱(傳入不包含數字的名稱，非裝置綁定的體驗角色)
	 */
	public static boolean isTrialPlayerName(String playerName){
		if(!isTrialPlayerNamesInit){
			initTrialAccountPlayerName();
		}
		
		if(!GameUtility.isHaveString(playerName)){
			return false;
		}
		
		if(!trialPlayerNames.containsValue(playerName)){
			return false;
		}
		
		return true;
	}
	
}